package com.boot.security.service.database.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.boot.persistence.service.ServiceImpl;
import com.boot.security.mapper.AccessMapper;
import com.boot.security.mapper.AccessResourceMapper;
import com.boot.security.mapper.UserAccessMapper;
import com.boot.security.model.po.Access;
import com.boot.security.model.po.AccessResource;
import com.boot.security.model.po.UserAccess;
import com.boot.security.service.database.AccessResourceService;
import com.boot.security.service.database.AccessService;
import com.boot.security.service.database.UserAccessService;
import com.boot.web.exception.GlobalException;
import com.boot.web.http.Status;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2019/8/24 10:11
 **/

@Service
public class AccessServiceImpl extends ServiceImpl<Long, Access, AccessMapper> implements AccessService {

    private final UserAccessService userAccessService;

    private final AccessResourceService accessResourceService;

    public AccessServiceImpl(AccessMapper mapper, UserAccessService userAccessService, AccessResourceService accessResourceService) {
        super(mapper);
        this.userAccessService = userAccessService;
        this.accessResourceService = accessResourceService;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(Access entity) {
        boolean result = super.save(entity);
        if (result && entity.getParent() != null) {
            List<UserAccess> parentList = userAccessService.list(Wrappers.query(UserAccess.builder()
                    .accessId(entity.getParent())
                    .build()));
            parentList.stream().map(v -> UserAccess.builder()
                            .userId(v.getUserId())
                            .accessId(entity.getId())
                            .build())
                    .distinct()
                    .map(v -> userAccessService.getMapper().insert(v))
                    .reduce(Integer::sum)
                    .ifPresent(columns -> {
                        if (columns != parentList.size()) {
                            throw new GlobalException(Status.INSERT_FAILURE, HttpStatus.BAD_REQUEST);
                        }
                    });
        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeById(Long id) {
        Access access = mapper.selectById(id);
        handleUserAccessChildrenInDelete(access);
        handleUserAccessParentInDelete(access);
        handleAccessResourceChildrenInDelete(access);
        handleAccessResourceParentInDelete(access);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeByIds(Collection<? extends Long> idList) {
        idList.forEach(this::removeById);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean remove(Wrapper<Access> queryWrapper) {
        List<Access> accesses = mapper.selectList(queryWrapper);
        for (Access access : accesses) {
            handleUserAccessChildrenInDelete(access);
            handleUserAccessParentInDelete(access);
            handleAccessResourceChildrenInDelete(access);
            handleAccessResourceParentInDelete(access);
        }
        return true;
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 见 Authority.md 中 Delete Authority
     * @date 2021/3/20 15:13
     **/
    public void handleUserAccessChildrenInDelete(Access entity) {
        List<Access> children = mapper.selectList(Wrappers.query(Access.builder()
                .parent(entity.getId())
                .build()));
        if (CollectionUtils.isEmpty(children)) {
            // 1 4
            userAccessService.remove(Wrappers.query(UserAccess.builder()
                    .accessId(entity.getId())
                    .build()));
        } else {
            // 2 3 5
            children.forEach(this::handleUserAccessChildrenInDelete);
        }
        mapper.deleteById(entity.getId());
    }

    public void handleAccessResourceChildrenInDelete(Access entity) {
        List<Access> children = mapper.selectList(Wrappers.query(Access.builder()
                .parent(entity.getId())
                .build()));
        if (CollectionUtils.isEmpty(children)) {
            // 1 4
            accessResourceService.remove(Wrappers.query(AccessResource.builder()
                    .accessId(entity.getId())
                    .build()));
        } else {
            // 2 3 5
            children.forEach(this::handleAccessResourceChildrenInDelete);
        }
        mapper.deleteById(entity.getId());
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 见 Authority.md 中 Delete Authority
     * @date 2021/3/20 15:13
     **/
    public void handleUserAccessParentInDelete(Access entity) {
        if (entity.getParent() != null) {
            Access parent = mapper.selectById(entity.getParent());
            List<Long> other = mapper.selectList(Wrappers.query(Access.builder()
                            .parent(parent.getId())
                            .build()))
                    .stream().map(Access::getId).collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(other)) {
                // 7 9 10 11
                UserAccessMapper userAccessMapper = userAccessService.getMapper();
                List<Long> userIds = userAccessMapper.selectUserIdByAccessIds(other);
                AtomicInteger insertColumns = new AtomicInteger();
                userIds.forEach(userId -> {
                    // 7 10
                    UserAccess userAccess = UserAccess.builder()
                            .userId(userId)
                            .accessId(parent.getId())
                            .build();
                    long columns = userAccessMapper.selectCount(Wrappers.query(userAccess));
                    if (columns == 0) {
                        userAccessMapper.insert(userAccess);
                        insertColumns.getAndIncrement();
                    }
                });
                if (insertColumns.get() > 0) {
                    handleUserAccessParentInDelete(parent);
                }
            }
            // else 6 8
        }
    }

    public void handleAccessResourceParentInDelete(Access entity) {
        if (entity.getParent() != null) {
            Access parent = mapper.selectById(entity.getParent());
            List<Long> other = mapper.selectList(Wrappers.query(Access.builder()
                            .parent(parent.getId())
                            .build()))
                    .stream().map(Access::getId).collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(other)) {
                // 7 9 10 11
                AccessResourceMapper accessResourceMapper = accessResourceService.getMapper();
                List<Long> resourceIds = accessResourceMapper.selectResourceIdByAccessIds(other);
                AtomicInteger insertColumns = new AtomicInteger();
                resourceIds.forEach(resourceId -> {
                    // 7 10
                    AccessResource accessResource = AccessResource.builder()
                            .resourceId(resourceId)
                            .accessId(parent.getId())
                            .build();
                    long columns = accessResourceMapper.selectCount(Wrappers.query(accessResource));
                    if (columns == 0) {
                        accessResourceMapper.insert(accessResource);
                        insertColumns.getAndIncrement();
                    }
                });
                if (insertColumns.get() > 0) {
                    handleAccessResourceParentInDelete(parent);
                }
            }
            // else 6 8
        }
    }

    @Override
    public Access createModel() {
        return new Access();
    }
}
